#include "part_group_cache.h"
#include "part2d.h"
#include "part_group.h"
#include "part_die.h"
#include "p2d_force.h"

#include "util/common_macros.h"

using namespace part2d;

PartGroupCache::PartGroupCache(Part2D* pSys, CACHE_DEFAULTS* pDefault, int CacheSize) :
	m_pSys(pSys),
	m_CacheSize(CacheSize),
	m_SearchIdx(0),
	m_SearchGroup(0)
{
	if (m_DefaultEnabled = (pDefault ? true : false)) {
		m_Defaults.Force = pDefault->Force;
		m_Defaults.MaxC = pDefault->MaxC;
	}

	Init();
}

PartGroupCache::~PartGroupCache()
{
	Clear();
}

void PartGroupCache::Clear()
{
	for (int i = 0; i < (int)m_Cache.size(); i++)
	{
		for (int j = 0; j < m_CacheSize; j++)
			UTIL_SAFE_DELETE(m_Cache[i]->pGroupsA[j]);
		UTIL_SAFE_DELETEA(m_Cache[i]->pGroupsA);
		UTIL_SAFE_DELETEA(m_Cache[i]->AllocA);
		UTIL_SAFE_DELETE(m_Cache[i]);
	}
	m_Cache.clear();

}

void PartGroupCache::Init(Part2D* pNewSystem)
{
	if (pNewSystem) Clear();
	m_SearchIdx = 0;
	m_SearchGroup = 0;
	Alloc();
}

PartGroup* PartGroupCache::GetFromCache(Part2D* pRefSys, PartGroup* pRefCopy, unsigned long flags)
{
	PartGroup* pGroup = NULL;
	CACHE_BLOCK* pBlock = NULL;
	int Idx = -1;
	int GroupC = (int)m_Cache.size();

	for (int i = 0; i < GroupC; i++)
	{
		m_SearchGroup = (m_SearchGroup + i) % GroupC;
		if (m_Cache[m_SearchGroup]->AllocSize >= m_CacheSize) continue;

		for (int j = 0; j < m_CacheSize; j++)
		{
			m_SearchIdx = (m_SearchIdx + j) % m_CacheSize;

			if (!m_Cache[m_SearchGroup]->AllocA[m_SearchIdx]) {
				Idx = m_SearchIdx;
				pBlock = m_Cache[m_SearchGroup];
				break;
			}
		}

		if (pBlock) break;
	}

	if (!pBlock) {
		pBlock = Alloc();
		Idx = 0;
		m_SearchGroup = 0;
		m_SearchIdx = 1;
	}

	pGroup = pBlock->pGroupsA[Idx];
	pBlock->AllocA[Idx] = 1;
	pBlock->AllocSize++;

	if (pRefSys) {
		pGroup->m_pSystem = pRefSys;
		flags &= ~CF_COPYSYS;
	}
	if (pRefCopy) CopyGroup(pGroup, pRefCopy, flags);
	return pGroup;
}

bool PartGroupCache::Release(PartGroup* pGroup)
{
	for (int i = 0; i < (int)m_Cache.size(); i++)
	{
		for (int j = 0; j < m_CacheSize; j++)
		{
			if (m_Cache[i]->AllocA[j] && (pGroup == m_Cache[i]->pGroupsA[j]))
			{
				m_Cache[i]->AllocA[j] = 0;
				m_Cache[i]->AllocSize--;
				pGroup->m_pSystem = m_pSys;
				return true;
			}
		}
	}

	return false;
}

void PartGroupCache::Reset(PartGroup* pGroup)
{
	for (int i = 0; i < pGroup->m_MaxC; i++)
		pGroup->m_Particles[i].Active = false;

	pGroup->m_PartC = pGroup->m_LastActive = 0;
}

PartGroupCache::CACHE_BLOCK* PartGroupCache::Alloc()
{
	CACHE_BLOCK* pBlock = new CACHE_BLOCK;

	pBlock->AllocSize = 0;

	pBlock->AllocA = new long[m_CacheSize];
	memset(pBlock->AllocA, 0, m_CacheSize*sizeof(long));

	pBlock->pGroupsA = new PartGroup*[m_CacheSize];
	for (int i = 0; i < m_CacheSize; i++)
	{
		pBlock->pGroupsA[i] = new PartGroup(m_pSys);
		if (m_DefaultEnabled) {
			if (!m_Defaults.Force.empty())
				pBlock->pGroupsA[i]->CreateForce(m_Defaults.Force);
			if (m_Defaults.MaxC > 0)
				pBlock->pGroupsA[i]->SetMaxC(m_Defaults.MaxC);
		}
	}

	m_Cache.push_back(pBlock);
	return pBlock;
}

void PartGroupCache::CopyGroup(PartGroup* pGroup, const PartGroup* pRef, unsigned long flags)
{
	if (flags & CF_COPYSYS)
		pGroup->m_pSystem = pRef->m_pSystem;

	pGroup->m_Additive = pRef->m_Additive;
	pGroup->m_LockRect = pRef->m_LockRect;
	pGroup->m_LockSize = pRef->m_LockSize;
	pGroup->m_FuzzyBirth = pRef->m_FuzzyBirth;
	pGroup->m_SizeFromMin = pRef->m_SizeFromMin;
	pGroup->m_SizeFromMax = pRef->m_SizeFromMax;
	pGroup->m_SizeToMin = pRef->m_SizeToMin;
	pGroup->m_SizeToMax = pRef->m_SizeToMax;
	pGroup->m_Lifespan = pRef->m_Lifespan;
	pGroup->m_MinSpeed = pRef->m_MinSpeed;
	pGroup->m_MaxSpeed = pRef->m_MaxSpeed;
	pGroup->m_MinSpeedMag = pRef->m_MinSpeedMag;
	pGroup->m_StretchSpeed = pRef->m_StretchSpeed;
	pGroup->m_GenRate = pRef->m_GenRate;
	pGroup->m_IsSecondary = pRef->m_IsSecondary;
	pGroup->m_TriggerIndex = pRef->m_TriggerIndex;

	if (flags & CF_COPYMAXC) {
		if (pGroup->m_MaxC != pRef->m_MaxC) {
			pGroup->SetMaxC(pRef->m_MaxC);
			flags &= ~CF_DEACTIVATE;
		}
	}

	if (flags & CF_DEACTIVATE)
		Reset(pGroup);

	if (flags & CF_COPYIMAGE)
		pGroup->SetImage(pRef->m_Image);

	if (flags & CF_COPYCOLORS)
	{
		if (!pRef->m_BaseColors.empty())
		{
			pGroup->m_BaseColors.reserve(pRef->m_BaseColors.size());
			for (unsigned int i = 0; i < pRef->m_BaseColors.size(); i++)
				pGroup->m_BaseColors.push_back(pRef->m_BaseColors[i]);
		}
		else
			pGroup->m_BaseColors.clear();

		pGroup->RecalcColors();
	}

	if (flags & CF_COPYONDIE)
	{
		UTIL_SAFE_DELETE(pGroup->m_pOnDie);
		pGroup->m_pOnDie = pRef->m_pOnDie ? PartDie::GetPartDie(pRef->m_pOnDie, pGroup->m_pSystem) : NULL;
	}

	if (flags & CF_COPYFORCES)
	{
		for (unsigned int i = 0; i < pGroup->m_Forces.size(); i++)
			UTIL_SAFE_DELETE(pGroup->m_Forces[i]);
		pGroup->m_Forces.clear();

		for (unsigned int j = 0; j < pRef->m_Forces.size(); j++)
			pGroup->m_Forces.push_back(pRef->m_Forces[j]->Clone());
	}
}
